﻿#light

namespace pwestcott.Util

open System
open System.Collections.Generic
open System.Linq
open System.Linq.Expressions
open System.Reflection
open System.Text

[<Flags>]
type EssenceFunctions =
    | None          = 0x0000
    | GetHashCode   = 0x0001
    | Equals        = 0x0002
    | CompareTo     = 0x0004
    | All           = 0xFFFF

[<Flags>]
type EssenceCompareToOptions =
    | None          = 0x0000
    | Reverse       = 0x0001

[<Flags>]
type EqualsOptions =
    | None              = 0x0000
    | NullNotEqualNull  = 0x0001 

type EssenceAttribute() =
    inherit System.Attribute()
    
    let mutable useIn = EssenceFunctions.All
    let mutable order = System.Int32.MaxValue
    let mutable nullCheck = Nullable<bool>()
    let mutable format = "{0}"
    let mutable compareToOptions = EssenceCompareToOptions.None
    
    member this.UseIn
        with get () = useIn
        and  set ui = useIn <- ui
    member this.Order
        with get () = order
        and  set o  = order <- o
    member this.NullCheck
        with get () = nullCheck
        and  set nc = nullCheck <- nc
    member this.Format
        with get () = format
        and  set f  = format <- f
    member this.CompareToOptions
        with get () = compareToOptions
        and  set o  = compareToOptions <- o


type EssenceClassAttribute() =
    inherit System.Attribute()
    
    let mutable useIn = EssenceFunctions.GetHashCode ||| EssenceFunctions.Equals
    let mutable nullCheck = true
    
    member this.UseIn
        with get () = useIn
        and  set ui = useIn <- ui
    member this.NullCheck
        with get () = nullCheck
        and  set nc = nullCheck <- nc

type EssenceComparer<'a>(compare : Func<'a, 'a, int>) =
    inherit Comparer<'a>()

    override this.Compare (lhs, rhs) =
        compare.Invoke(lhs, rhs)

type EssenceEqualityComparer<'a>(equals : Func<'a, 'a, bool>, getHashCode : Func<'a, int>) =
    inherit EqualityComparer<'a>()

    override this.Equals (lhs, rhs) =
        equals.Invoke(lhs, rhs)
    override this.GetHashCode obj =
        getHashCode.Invoke(obj)

// START EXPERIMENTAL
type EssenceCompareToResult(value : int) =
    member this.Value = value
// END EXPERIMENTAL
    
type EssenceComparerAction() =
    member this.ReverseNext
        with get () = null
    member this.ReverseRemaining
        with get () = null
    
type EssenceComparerStream<'a>(theClass : 'a) =
    let action = EssenceComparerAction()
    member this.Member
        with get () = theClass
    member this.Action
        with get () = action
        
type Essence<'a>() =
    static let eAnd lhs rhs                  = Expression.And(lhs, rhs)
    static let eAndAlso lhs rhs              = Expression.AndAlso(lhs, rhs)
    static let eCall (obj : Expression) name = Expression.Call(obj, name, null, null)
    static let eCallStatic (name : MethodInfo) (args : #seq<Expression>) = Expression.Call(name, args.ToArray())
    static let eCall3 (obj : Expression) (name : MethodInfo) (args : #seq<Expression>) = Expression.Call(obj, name, args.ToArray())
    static let eConst obj                    = Expression.Constant(obj)
    static let eEqual lhs rhs                = Expression.Equal(lhs, rhs)
    static let eEqual3 lhs rhs op            = Expression.Equal(lhs, rhs, false, op)
    static let eIfThenElse check tru flse    = Expression.Condition(check, tru, flse)
    static let eParam name                   = Expression.Parameter(typeof<'a>, name)
    static let eMember param name            = Expression.PropertyOrField(param, name)
    static let eMult lhs rhs                 = Expression.Multiply(lhs, rhs)
    static let eNotEqual lhs rhs             = Expression.NotEqual(lhs, rhs)
    static let eXor lhs rhs                  = Expression.ExclusiveOr(lhs, rhs)

    static let eFalse       = eConst false
    static let eTrue        = eConst true
    static let eNull        = eConst null
    static let eZero        = eConst 0
    static let eOne         = eConst 1
    static let eNegOne      = eConst -1
    static let eThisType    = eConst (typeof<'a>)
    static let eEmptyString = eConst System.String.Empty
    
    static let HashMultiplierValue = 347
    static let HashMultiplier = eConst HashMultiplierValue
    
    static let eIsNull obj = eEqual obj eNull
    static let eIsNotNull obj = eNotEqual obj eNull
    
    static let attClass =
        let att = typeof<'a>.GetCustomAttributes(typeof<EssenceClassAttribute>, false)
        if att.Length > 0 then
            att.[0] :?> EssenceClassAttribute
        else
            EssenceClassAttribute()

    static let getMembers which =
        let hasEssence = fun (x : MemberInfo) ->
            let atr = x.GetCustomAttributes(typeof<EssenceAttribute>, false)
            if atr.Length > 0 then Some(atr.[0] :?> EssenceAttribute) else None

        let flags = BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance            
        
        let fields = Seq.cast<MemberInfo> (typeof<'a>.GetFields(flags))
        let properties = Seq.cast<MemberInfo> (typeof<'a>.GetProperties(flags))

        properties |>
        Seq.append fields |>
        Seq.map (fun m -> hasEssence m, m) |>
        Seq.filter (function
            | Some(a), _  when a.UseIn &&& which = which -> true
            | _ -> false) |>
        Seq.map (fun x -> (fst x).Value, (snd x)) |>
        Seq.sort_by (fun x -> (fst x).Order)
        
    static let getMembersFromNames (names : #seq<string>) =
        let members = 
            names |>
            Seq.map (fun name -> name, name.ElementAt(0), name.Substring(1)) |>
            Seq.map (function
                        | name, first, remaining when first.Equals('-') -> EssenceAttribute(CompareToOptions=EssenceCompareToOptions.Reverse), remaining
                        | name, _, _ -> EssenceAttribute(), name) |>
            Seq.map (function att, name -> att, typeof<'a>.GetMember(name).[0])

        members

    static let rec getMemberFromExpression (expression : Expression) = 
        match expression with
        | :? LambdaExpression as lambda -> getMemberFromExpression lambda.Body
        | :? UnaryExpression as unary when unary.NodeType = ExpressionType.Convert -> getMemberFromExpression unary.Operand
        | :? MemberExpression as mber -> mber.Member
        | _ -> failwith "Lambda expression is more complex than expected."
                                
    static let getMembersFromExpressions (members : Expression<Func<'a, System.Object>> array) =
        members |>
        Seq.map (fun f -> EssenceAttribute(), getMemberFromExpression f)

    static let getMembersFromComparerStream (members : Expression<Func<EssenceComparerStream<'a>, System.Object>> array) =
        let rec parseStream nodesAndTypes reverse =
            let isAction node = (node = typeof<EssenceComparerAction>)
            let isEssenceType node = (node = typeof<'a>)
                
            let createAttribute reverse (action : Option<MemberInfo>) = 
                let doReverse =
                    let isReverseAction (a : MemberInfo) =
                        Seq.exists (fun n -> n = a.Name) ["ReverseNext";"ReverseRemaining"]
                
                    match action with 
                    | Some(a) when isReverseAction a -> not reverse
                    | _ -> reverse
                
                let attribute = EssenceAttribute()
                if doReverse then
                    attribute.CompareToOptions <- EssenceCompareToOptions.Reverse
                
                attribute
                
            let nextReverse reverse (action : MemberInfo) = 
                if (action.Name = "ReverseRemaining")  then
                    not reverse
                else
                    reverse
                
            match nodesAndTypes with
                | [] -> []
                | (action, actionType) :: (mber, mberType) :: remaining when isAction actionType ->
                    (createAttribute reverse (Some(action)), mber) :: parseStream remaining (nextReverse reverse action)
                | (action, actionType) :: remaining when isAction actionType ->
                    parseStream remaining (nextReverse reverse action)
                | (mber, mberType) :: remaining -> 
                    (createAttribute reverse None, mber) :: parseStream remaining reverse

        let nodesAndTypes = 
            members |>
            Seq.map getMemberFromExpression |>
            Seq.map (fun info -> info, info.DeclaringType) |>
            Seq.fold (fun tl hd -> hd :: tl) [] |>
            List.rev
                
        parseStream nodesAndTypes false
        
    static let getType (mi : MemberInfo) =
        match mi with
        | :? PropertyInfo as p -> p.PropertyType
        | :? FieldInfo as f -> f.FieldType
        | _ -> failwith "Invalid member info type"

    static let shouldNullCheck (attClass : EssenceClassAttribute) (att : EssenceAttribute) i =
        let t = getType i
        t.IsClass && (if att.NullCheck.HasValue then att.NullCheck.Value else attClass.NullCheck)

    // GetHashCode
        
    static let createGetHashCodeExpressionTree eThis attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        if Seq.is_empty members then
            eZero :> Expression
        else
            let nullCheck mbr hashcode =
                let isNull = eIsNull mbr
                eIfThenElse isNull eZero hashcode
        
            let calls =
                members |>
                Seq.map (function att, info -> att, info, (eMember eThis info.Name)) |>
                Seq.map (function att, info, mbr -> att, info, mbr, (eCall mbr "GetHashCode")) |>
                Seq.map (function
                    | att, info, mbr, hashcode when shouldNullCheck attClass att info -> nullCheck mbr hashcode :> Expression
                    | _, _, _, hashcode -> hashcode :> Expression)
                
            let first = calls |> Seq.hd
            let rest = Seq.skip 1 calls |> Seq.cast<Expression>

            let xored =
                rest |>
                Seq.fold (fun total next -> (eXor (eMult total HashMultiplier) next) :> Expression) first

            xored
                
    static let createGetHashCodeStatic attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let this = eParam "this"
        let getHashCode = createGetHashCodeExpressionTree this attClass members
        let lambda = Expression.Lambda<Func<'a, int>>(getHashCode, [this])
        lambda.Compile()

    static let createGetHashCodeInstance obj attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let this = eConst obj
        let getHashCode = createGetHashCodeExpressionTree this attClass members
        let lambda = Expression.Lambda<Func<int>>(getHashCode, [])
        lambda.Compile()

    // Equals

    static let createEqualsExpressionTree lhs rhs attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        if Seq.is_empty members then
            eTrue :> Expression
        else
            let getEqualsMethod info =
                let t = getType info
                let equatable = t.GetInterface("IEquatable`1")
                match equatable with
                    | null -> t.GetMethod("Equals", [|typeof<System.Object>|])
                    | _ -> equatable.GetMethod("Equals", [|t|])
            
            let callEquals info lhs rhs =
                let eqls = getEqualsMethod info
                Expression.Call(lhs, eqls, [rhs])
                
            let noOpEquality att info lmbr rmbr eqls otherwise =
                let chain = eAndAlso eqls otherwise
                match shouldNullCheck attClass att info with
                    | false -> chain :> Expression
                    | _ ->
                        let isLmbrNull = eIsNull lmbr
                        let isRmbrNull = eIsNull rmbr
                        let whenLmbrNull = eIfThenElse isRmbrNull otherwise eFalse
                        eIfThenElse isLmbrNull whenLmbrNull chain :> Expression
                        
            let useOpEquality opEquality lmbr rmbr otherwise =
                let equals = eEqual3 lmbr rmbr opEquality
                eAndAlso equals otherwise :> Expression
                
            let equalsCheck att info lmbr rmbr eqls otherwise =
                let t = getType info
                match t.GetMethod("op_Equality", [|t;t|]) with
                | null -> noOpEquality att info lmbr rmbr eqls otherwise
                | opEquality -> useOpEquality opEquality lmbr rmbr otherwise
                
            let eqls =
                members |>
                Seq.to_list |>
                List.rev |>
                Seq.map (function att, info -> att, info, eMember lhs info.Name, eMember rhs info.Name) |>
                Seq.map (function att, info, lmbr, rmbr -> att, info, lmbr, rmbr, callEquals info lmbr rmbr) |>
                Seq.fold (fun otherwise next ->
                    match next with att, info, lmbr, rmbr, eqls -> equalsCheck att info lmbr rmbr eqls otherwise) (eTrue :> Expression)
                    
            let eqlsEntry =
                if not (typeof<'a>.IsClass) then
                    eqls
                else
                    let getReferenceEquals = typeof<System.Object>.GetMethod("ReferenceEquals")
            
                    let refCheck = Expression.Call(getReferenceEquals, [|(lhs :> Expression); (rhs :> Expression)|])
                    // TODO: If I want to support some inheritance, then this should be changed back.
                    // But it probably shouldn't be the default, or maybe not available at all if you inherit
                    // from IEquatable<'a>.
                    // let typeCheck = eEqual (eCall lhs "GetType") (eCall rhs "GetType")
                    let typeCheck = eEqual eThisType (eCall rhs "GetType")
                    let isRhsNull = eIsNull rhs
                
                    let doTypeCheck = eIfThenElse typeCheck eqls eFalse
                    let doRefCheck = eIfThenElse refCheck eTrue doTypeCheck
                    let doRhsNullCheck = eIfThenElse isRhsNull eFalse doRefCheck
                    
                    doRhsNullCheck :> Expression
                    
            eqlsEntry

    static let createEqualsStatic attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let lhs = eParam "lhs"
        let rhs = eParam "rhs"

        let equals = createEqualsExpressionTree lhs rhs attClass members
        let eqlsEntry =
            if not (typeof<'a>.IsClass) then
                equals
            else
                // Add null check for lhs
                let isLhsNull = eIsNull lhs
                let isRhsNull = eIsNull rhs
                let whenLhsNullCheck = eIfThenElse isRhsNull eTrue eFalse
                let doLhsNullCheck = eIfThenElse isLhsNull whenLhsNullCheck equals
                
                doLhsNullCheck :> Expression
        
        let lambda = Expression.Lambda<Func<'a, 'a, bool>>(eqlsEntry, [lhs; rhs])
        lambda.Compile()
    
    static let createEqualsInstance obj attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let lhs = eConst obj
        let rhs = eParam "rhs"
        let equals = createEqualsExpressionTree lhs rhs attClass members
        let lambda = Expression.Lambda<Func<'a, bool>>(equals, [rhs])
        lambda.Compile()

    // CompareTo

    static let createCompareToExpressionTree lhs rhs attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        if Seq.is_empty members then
            eZero :> Expression
        else
            let getCompareToMethod info =
                let t = getType info
                let comparable = t.GetInterface("IComparable`1")
                match comparable with
                    | null ->
                        let comparable = t.GetInterface("IComparable")
                        match comparable with
                            | null -> failwith "All Essence members need to support IComparable or IComparable<T>"
                            | _ -> comparable.GetMethod("CompareTo", [|typeof<System.Object>|])
                    | _ -> comparable.GetMethod("CompareTo", [|t|])
            
            let eCompareTo info lmbr rmbr =
                let eqls = getCompareToMethod info
                Expression.Call(lmbr, eqls, [rmbr])
                
            let maybeReverse (att : EssenceAttribute) compare =
                if att.CompareToOptions &&& EssenceCompareToOptions.Reverse = EssenceCompareToOptions.Reverse then
                    eMult compare eNegOne :> Expression
                else
                    compare :> Expression

            let eCheckMember att info lmbr rmbr compare otherwise =
                let checkIfZero = eEqual compare eZero
                let check = eIfThenElse checkIfZero otherwise compare
                let check = 
                    if not (shouldNullCheck attClass att info) then
                        check
                    else
                        let isLmbrNull = eIsNull lmbr
                        let isRmbrNull = eIsNull rmbr
                        let whenLmbrNull = eIfThenElse isRmbrNull otherwise eNegOne
                        eIfThenElse isLmbrNull whenLmbrNull check
                check :> Expression
                
            let compareTo =
                members |>
                Seq.to_list |>
                List.rev |>
                Seq.map (function att, info -> att, info, eMember lhs info.Name, eMember rhs info.Name) |>
                Seq.map (function att, info, lmbr, rmbr -> att, info, lmbr, rmbr, eCompareTo info lmbr rmbr) |>
                Seq.map (function att, info, lmbr, rmbr, compare -> att, info, lmbr, rmbr, (maybeReverse att compare)) |>
                Seq.fold (fun otherwise next ->
                    match next with att, info, lmbr, rmbr, compare -> eCheckMember att info lmbr rmbr compare otherwise) (eZero :> Expression)

            let compareToEntry =
                if not (typeof<'a>.IsClass) then
                    compareTo
                else
                    let getReferenceEquals = typeof<System.Object>.GetMethod("ReferenceEquals")
            
                    let refCheck = Expression.Call(getReferenceEquals, [|(lhs :> Expression); (rhs :> Expression)|])
                    let isLhsNull = eIsNull lhs
                    let isRhsNull = eIsNull rhs
                
                    let doRefCheck = eIfThenElse refCheck eZero compareTo
                    let doRhsNullCheck = eIfThenElse isRhsNull eOne doRefCheck
                    let whenLhsNull = eIfThenElse isRhsNull eZero eNegOne
                    let doLhsNullCheck = eIfThenElse isLhsNull whenLhsNull doRhsNullCheck
                    
                    doLhsNullCheck :> Expression

            compareToEntry

    static let createCompareToStatic attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let lhs = eParam "lhs"
        let rhs = eParam "rhs"
        let compareTo = createCompareToExpressionTree lhs rhs attClass members
        let lambda = Expression.Lambda<Func<'a, 'a, int>>(compareTo, [lhs; rhs])
        lambda.Compile()
    
    static let createCompareToInstance obj attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let lhs = eConst obj
        let rhs = eParam "rhs"
        let compareTo = createCompareToExpressionTree lhs rhs attClass members
        let lambda = Expression.Lambda<Func<'a, int>>(compareTo, [rhs])
        lambda.Compile()
        
    // ToString
        
    static let createToStringExpressionTree eThis attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        if Seq.is_empty members then
            eEmptyString :> Expression
        else
            let nullCheck mbr toString =
                let isNull = eIsNull mbr
                eIfThenElse isNull eEmptyString toString

            let getAppend = typeof<StringBuilder>.GetMethod("Append", [|typeof<String>|])
            let getStringFormat = typeof<string>.GetMethod("Format", [|typeof<String>;typeof<Object>|])
            let getConcat = typeof<string>.GetMethod("Concat", [|typeof<String>;typeof<String>|])
            let getFormat (att : EssenceAttribute) = (eConst att.Format) :> Expression
            
            let calls =
                members |>
                Seq.map (function att, info -> att, info, (eMember eThis info.Name)) |>
                Seq.map (function att, info, mbr -> att, info, mbr, (eCallStatic getStringFormat [ getFormat att; (Expression.Convert(mbr, typeof<Object>) :> Expression) ])) |>
                Seq.map (function
                    | att, info, mbr, formatted when shouldNullCheck attClass att info -> nullCheck mbr formatted :> Expression
                    | _, _, _, formatted -> formatted :> Expression)
                        
            let combined =
                calls |>
                Seq.fold (fun all next -> (eCallStatic getConcat [ all; next ]) :> Expression) (eEmptyString :> Expression)
            
            combined
        
    static let createToStringStatic attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let this = eParam "obj"
        let toString = createToStringExpressionTree this attClass members
        let lambda = Expression.Lambda<Func<'a, string>>(toString, [this])
        lambda.Compile()
    
    static let createToStringInstance obj attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let this = eConst obj
        let toString = createToStringExpressionTree this attClass members
        let lambda = Expression.Lambda<Func<string>>(toString, [])
        lambda.Compile()
        
    // START EXPERIMENTAL
    
    static let createCompareToFast attClass (members : seq<(EssenceAttribute * MemberInfo)>) =
        let lhs = eParam "lhs"
        let rhs = eParam "rhs"

        let getCompareToMethod info =
            let t = getType info
            let comparable = t.GetInterface("IComparable`1")
            match comparable with
                | null ->
                    let comparable = t.GetInterface("IComparable")
                    match comparable with
                        | null -> failwith "All Essence members need to support IComparable or IComparable<T>"
                        | _ -> comparable.GetMethod("CompareTo", [|typeof<System.Object>|])
                | _ -> comparable.GetMethod("CompareTo", [|t|])
        
        let callCompareTo info lmbr rmbr =
            let eqls = getCompareToMethod info
            Expression.Call(lmbr, eqls, [rmbr]) :> Expression

        let addNullCheck att info lmbr rmbr compare =
            let check = 
                if not (shouldNullCheck attClass att info) then
                    compare :> Expression
                else
                    let isLmbrNull = eIsNull lmbr
                    let isRmbrNull = eIsNull rmbr
                    let whenLmbrNull = eIfThenElse isRmbrNull eZero eNegOne
                    eIfThenElse isLmbrNull whenLmbrNull compare :> Expression
            check
    
        let createComparer att info lmbr rmbr =
            let call = callCompareTo info lmbr rmbr
            let compareTo = addNullCheck att info lmbr rmbr call
            let con = typeof<EssenceCompareToResult>.GetConstructor([|typeof<int>|]);
            let cache = Expression.New(con, [compareTo])
            let lambda = Expression.Lambda<Func<'a, 'a, EssenceCompareToResult>>(cache, [lhs; rhs])
            lambda.Compile()

        let eCheckMember result otherwise =
            let getValue = Expression.PropertyOrField(result, "Value");
            let checkIfZero = eEqual getValue eZero
            let check = eIfThenElse checkIfZero otherwise getValue
            check :> Expression
    
        let c =
            members |>
            Seq.map (function att, info -> att, info, eMember lhs info.Name, eMember rhs info.Name) |>
            Seq.map (function att, info, lmbr, rmbr -> att, info, lmbr, rmbr, createComparer att info lmbr rmbr)


        c |> Seq.iter (fun x -> 1 |> ignore)
    
        0

    static let compareToFastStatic     = createCompareToFast attClass (getMembers EssenceFunctions.CompareTo)
    // END EXPERIMENTAL
                
    static let getHashCodeStatic       = createGetHashCodeStatic attClass (getMembers EssenceFunctions.GetHashCode)
    static let getHashCodeInstance obj = createGetHashCodeInstance obj attClass (getMembers EssenceFunctions.GetHashCode)
    static let equalsStatic            = createEqualsStatic attClass (getMembers EssenceFunctions.Equals)
    static let equalsInstance obj      = createEqualsInstance obj attClass (getMembers EssenceFunctions.Equals)
    static let compareToStatic         = createCompareToStatic attClass (getMembers EssenceFunctions.CompareTo)
    static let compareToInstance obj   = createCompareToInstance obj attClass (getMembers EssenceFunctions.CompareTo)
    static let toStringStatic          = createToStringStatic attClass (getMembers EssenceFunctions.CompareTo)
    static let toStringInstance obj    = createToStringInstance obj attClass (getMembers EssenceFunctions.CompareTo)

    static let compareToNamed members   = createCompareToStatic (EssenceClassAttribute()) (getMembersFromNames members)
    static let equalsNamed members      = createEqualsStatic (EssenceClassAttribute()) (getMembersFromNames members)
    static let getHashCodeNamed members = createGetHashCodeStatic (EssenceClassAttribute()) (getMembersFromNames members)

    static let compareToExpressions members   = createCompareToStatic (EssenceClassAttribute()) (getMembersFromExpressions members)
    static let equalsExpressions members      = createEqualsStatic (EssenceClassAttribute()) (getMembersFromExpressions members)
    static let getHashCodeExpressions members = createGetHashCodeStatic (EssenceClassAttribute()) (getMembersFromExpressions members)

    static let compareToStreamExpressions members   = createCompareToStatic (EssenceClassAttribute()) (getMembersFromComparerStream members)
    
    static member HashCodeMultiplier = HashMultiplierValue;
    
    static member GetHashCodeStatic       = (getHashCodeStatic : Func<'a, int>)
    static member GetHashCodeInstance obj = (getHashCodeInstance obj : Func<int>)
    static member EqualsStatic            = (equalsStatic : Func<'a, 'a, bool>)
    static member EqualsInstance obj      = (equalsInstance obj : Func<'a, bool>)
    static member CompareToStatic         = (compareToStatic : Func<'a, 'a, int>)
    static member CompareToInstance obj   = (compareToInstance obj : Func<'a, int>)
    static member ToStringStatic          = (toStringStatic : Func<'a, string>)
    static member ToStringInstance obj    = (toStringInstance obj : Func<string>)
    
    [<OverloadID("strings")>]
    static member CreateComparer ([<ParamArray>]members : string array) = 
        EssenceComparer(compareToNamed members) :> Comparer<'a>
        
    [<OverloadID("expressions")>]
    static member CreateComparer ([<ParamArray>]members : Expression<Func<'a, System.Object>> array) =
        EssenceComparer(compareToExpressions members) :> Comparer<'a>
        
    [<OverloadID("actions")>]
    static member CreateComparer ([<ParamArray>]members : Expression<Func<EssenceComparerStream<'a>, System.Object>> array) =
        EssenceComparer(compareToStreamExpressions members) :> Comparer<'a>
        
        
    [<OverloadID("strings")>]
    static member CreateEqualityComparer ([<ParamArray>]members : string array) =
        EssenceEqualityComparer(equalsNamed members, getHashCodeNamed members) :> EqualityComparer<'a>
        
    [<OverloadID("expressions")>]
    static member CreateEqualityComparer ([<ParamArray>]members : Expression<Func<'a, System.Object>> array) =
        EssenceEqualityComparer(equalsExpressions members, getHashCodeExpressions members) :> EqualityComparer<'a>


    // START EXPERIMENTAL
    static member Fast  = (compareToFastStatic : int)
    // END EXPERIMENTAL
